<?php

/**
 * 小程序服务商管理系统 v1.0.1
 *
 * 商业运营到axzz.cn官网进行授权使用。
 *
 * (c) cighsen02 <xiayu@959602.com>
 *
 * Create at：2025.05.03
 * Update at: 2025.06.25
*/

namespace App\Utils\Support;

use Exception;

use function array_pad;
use function array_reverse;
use function bcdiv;
use function bcmod;
use function bcmul;
use function bcpow;
use function dechex;
use function explode;
use function hexdec;
use function is_array;
use function is_numeric;
use function microtime;
use function mt_rand;
use function round;
use function sprintf;
use function strrev;

/**
 * 类似UUID自字义生成字符串
 */
class Uuid
{
    private const DIC = [
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
        'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'
    ];

    /**
     * 生成不重复唯一码
     *
     * @param int $classid
     * @param int $id
     * @param array<string, string> $option 其它设置项，option[serverId, clientIp, newstime] 分别：服务id, 客户IP, 发布时间截
     * @return string
     *
     * @throws Exception
     */
    public static function greate(int $classid = 0, int $id = 0, array $option = []): string
    {
        if($classid > 46655 || $id > PHP_INT_MAX) throw new Exception('超出范围', 9401);

        $t          = explode(" ", microtime());
        $t[1]       = $option['newstime'] ?? $t[1];
        $serverId   = $option['serverId'] ?? 1;
        $ip         = $option['clientIp'] ?? null;

        return sprintf(
            '%10s-%02x-%08s-%08s-%04s',
            0 !== $classid && 0 !== $id ? static::encodeId($classid, 3) . static::encodeId($id, 7) : static::encodeId(mt_rand(0,0xffff) . mt_rand(0,0xffff) . mt_rand(0,0xffff), 10),
            $serverId,
            static::clientIpToHex($ip),
            static::encodeId($t[1], 8), // get 8HEX of unixtime 时间截
            dechex(round($t[0] * 65536)) // get 4HEX of microtime
        );
    }

    /**
     * 解码
     *
     * @param string $uuid
     * @return array
     */
    public static function decode(string $uuid): array
    {
        $result = [];
        $u      = explode("-", $uuid);
        if(is_array($u) && 5 === count($u)) {
            $result = [
                'id'		=> $u[0],
                'serverID'	=> $u[1],
                'ip'		=> static::clientIpFromHex($u[2]),
                'unixtime'	=> static::decodeId($u[3]),
                'micro'		=> hexdec($u[4]) / 65536,
            ];
        }

        return $result;
    }

    /**
     * 十进制数转成字符串
     *
     * @param int $int 待加密数据
     * @param int $format 最低字符数
     * @return string
     *
     * @throws Exception
    */
    public static function encodeId(int $int, int $format = 4): string
    {
        if (!is_numeric($int)) throw new Exception('格式错误！', 9401);

        $dics   = static::DIC;
        $dnum   = count(static::DIC); //进制数
        $arr    = [];
        $loop   = true;
        while($loop) {

            $arr[]  = $dics[bcmod($int, $dnum)];
            $int    = bcdiv($int, $dnum, 0);
            if ($int === '0') $loop = false;
        }

        if (count($arr) < $format) $arr = array_pad($arr, $format, $dics[0]);

        return implode('', array_reverse($arr));
    }

    /**
     * 字符串转为十进制数
     *
     * @param string $ids 待转换字符串
     * @return int
     *
     * @throws Exception
    */
    public static function decodeId(string $ids): int
    {
        if (0 === strlen($ids) || str_contains($ids, '.')) throw new Exception('格式错误！', 9401);

        $dics   = static::DIC;
        $dnum   = count(static::DIC); // 进制数
        $dedic  = array_flip($dics); // 键值交换
        $id     = ltrim($ids, $dics[0]); // 去零
        $id     = strrev($id); // 反转
        $v      = 0;

        for ($i = 0, $j = strlen($id); $i < $j; $i++) {

            $v = bcadd(bcmul($dedic[$id[$i]], bcpow($dnum, $i, 0), 0), $v, 0);
        }

        return $v;
    }

    /**
     * ip2long
     *
     * @param string $ip
     * @return string
    */
    private static function clientIpToHex(?string $ip = null): string
    {
        $ip     ??= getEnv('REMOTE_ADDR');
        $part   = explode('.', $ip);
        $len    = count($part) - 1;
        $parts  = [];
        for ($i = 0; $i <= $len; $i++) {

            $parts[] = substr('0' . dechex($part[$i]), -2);
        }

        return implode('', $parts);
    }

    /**
     * 字符串转IP
     *
     * @param string $hex
     * @return string
    */
    public static function clientIpFromHex(string $hex): string
    {
        if ($hex && 8 === strlen($hex)) {

            return sprintf(
                '%d.%d.%d.%d',
                hexdec(substr($hex, 0, 2)),
                hexdec(substr($hex, 2, 2)),
                hexdec(substr($hex, 4, 2)),
                hexdec(substr($hex, 6, 2))
            );
        }

        return '';
    }
}
